/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Kernel Module                                  */
/*                                                                           */
/*    Copyright (C) 2008 Laszlo Arvai                                        */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    25LC256 EEPROM driver module                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Includes
#include <drvEEPROM.h>
#include <drvI2C.h>
#include <krnlSystemTimer.h>
#include "drvIOConfig.h"

///////////////////////////////////////////////////////////////////////////////
// Constants
#define EEPROM_GENERATE_FUNCTION_NAME_PREFIX(x,y,z) x ## y ## z
#define EEPROM_I2C_Start(x,y,z)		EEPROM_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Start)((y),(z))
#define EEPROM_I2C_WriteByte(x,y)	EEPROM_GENERATE_FUNCTION_NAME_PREFIX(drv, x, WriteByte)(y)
#define EEPROM_I2C_Restart(x,y,z)	EEPROM_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Restart)((y),(z))
#define EEPROM_I2C_ReadByte(x,y) 	EEPROM_GENERATE_FUNCTION_NAME_PREFIX(drv, x, ReadByte)(y)
#define EEPROM_I2C_Stop(x)				EEPROM_GENERATE_FUNCTION_NAME_PREFIX(drv, x, Stop)()

#define EEPROM_WRITE_TIMEOUT	10
#define EEPROM_I2C_ADDRESS_HIGH 0x50

///////////////////////////////////////////////////////////////////////////////
// Local functions
static dosBool drvEEPROMWaitForBusy(dosByte in_chip_address, dosWord in_timeout);

///////////////////////////////////////////////////////////////////////////////
//! Initializes EEPROM interface
void drvInitEEPROM( void)
{
	InitEEPROMI2C();
}

///////////////////////////////////////////////////////////////////////////////
//! Read block from EEPROM
//! \param Chip (I2C) address
//! \param Start address to read from
//! \param Buffer address to write to
//! \param Buffer length (number of bytes to read) 
//! \return dosTrue - if read is successfull
dosBool drvEEPROMReadBlock( dosByte in_chip_address, dosWord in_address, dosByte* out_buffer, dosWord in_length)
{
	dosWord i;
	dosByte value;
	
	EEPROM_I2C_Start(EEPROM_I2C, drvI2C_WRITE, EEPROM_I2C_ADDRESS_HIGH + in_chip_address);
	EEPROM_I2C_WriteByte(EEPROM_I2C, HIGH(in_address));
	EEPROM_I2C_WriteByte(EEPROM_I2C, LOW(in_address));
	EEPROM_I2C_Restart(EEPROM_I2C, drvI2C_READ, EEPROM_I2C_ADDRESS_HIGH + in_chip_address);
	
	// read block  
	for(i = 0; i < in_length; i++)
	{
		value = EEPROM_I2C_ReadByte(EEPROM_I2C, (i==in_length-1)?drvI2C_NACK:drvI2C_ACK);
		out_buffer[i]= value;
	}
	  
	EEPROM_I2C_Stop(EEPROM_I2C);
	
	return dosTrue;;
}

///////////////////////////////////////////////////////////////////////////////
//! Write block to EEPROM
//! \param Chip (I2C) address
//! \param Start address to write
//! \param Buffer address
//! \param Buffer length (number of bytes to write) 
//! \return dosTrue - if write is successfull
dosBool drvEEPROMWriteBlock( dosByte in_chip_address, dosWord in_address, dosByte* in_buffer, dosWord in_length)
{
	dosWord i;
	dosWord address = in_address;
	dosWord timestamp;
	
	EEPROM_I2C_Start(EEPROM_I2C, drvI2C_WRITE, EEPROM_I2C_ADDRESS_HIGH + in_chip_address);
	EEPROM_I2C_WriteByte(EEPROM_I2C, HIGH(in_address));		// address MSB first
	EEPROM_I2C_WriteByte(EEPROM_I2C, LOW(in_address));		// address LSB (word aligned)

	// write block data
	for( i = 0; i < in_length; i++ )
	{
		EEPROM_I2C_WriteByte(EEPROM_I2C, in_buffer[i]);
		address++;
		
		// write block at block border
		if( (address & 0x3f) == 0 )
		{
			// I2C stop condition
			EEPROM_I2C_Stop(EEPROM_I2C);
			
			// wait while eeprom is busy
			drvEEPROMWaitForBusy(EEPROM_I2C_ADDRESS_HIGH + in_chip_address, 20);
			
			if( i+1 < in_length )
			{
				timestamp = krnlGetSystemTimer();
				
				while( !EEPROM_I2C_Start(EEPROM_I2C, drvI2C_WRITE, EEPROM_I2C_ADDRESS_HIGH + in_chip_address) )
				{
					if( krnlGetTimeSince(timestamp) < EEPROM_WRITE_TIMEOUT)
					{
						EEPROM_I2C_Stop(EEPROM_I2C);
						krnlDelay(3);
					}
					else
					{
						EEPROM_I2C_Stop(EEPROM_I2C);
						return dosFalse; // write failed
					}
				}
				
				// send address
				EEPROM_I2C_WriteByte(EEPROM_I2C, HIGH(address));		// address MSB first
				EEPROM_I2C_WriteByte(EEPROM_I2C, LOW(address));		// address LSB (word aligned)
			}
		}
	}

	EEPROM_I2C_Stop(EEPROM_I2C);
	
	return dosTrue;
}

///////////////////////////////////////////////////////////////////////////////
// Waits while eeprom is busy with write 
// (true if timeout occured and EEPROM is still busy)
static dosBool drvEEPROMWaitForBusy(dosByte in_chip_address, dosWord in_timeout)
{ 
	//EEPROM_WRITE_TIMEOUT
	dosWord timestamp = krnlGetSystemTimer();
	
	// wait for eeprom to complete write process. poll the ack status
	while( krnlGetTimeSince(timestamp) < in_timeout )
	{
		if( EEPROM_I2C_Start(EEPROM_I2C, drvI2C_WRITE, EEPROM_I2C_ADDRESS_HIGH + in_chip_address) )
		{
			//eeprom has acknowledged
			EEPROM_I2C_Stop(EEPROM_I2C);
			return dosFalse;
		}

		EEPROM_I2C_Stop(EEPROM_I2C);
		krnlDelay(3);
	}
	
	// EEPROM is still busy -> communication failiure
	return dosTrue;
}
